"""
云存储模块
实现云存储服务（AWS S3、阿里云OSS等）
"""
import os
import boto3
from datetime import datetime, timedelta
from typing import BinaryIO, Dict, Any, List
from botocore.exceptions import ClientError, NoCredentialsError

from flask import current_app

from app.storage.base import StorageBase
from app.utils.exceptions import StorageError


class CloudStorage(StorageBase):
    """
    云存储实现（支持AWS S3、兼容S3的其他云存储）
    """
    
    def __init__(self, config: Dict[str, Any]):
        """
        初始化云存储
        
        Args:
            config: 存储配置
        """
        super().__init__(config)
        self.client = None
        self.bucket_name = config.get('bucket_name', '')
        self.region = config.get('region', 'us-east-1')
        self.provider = config.get('provider', 'aws_s3')  # aws_s3, ali_oss, qiniu等
        
    def setup(self):
        """设置云存储环境"""
        try:
            if self.provider == 'aws_s3':
                self._setup_aws_s3()
            elif self.provider == 'ali_oss':
                self._setup_ali_oss()
            else:
                self._setup_generic_s3()
            
            current_app.logger.info(f"云存储初始化完成: {self.provider}://{self.bucket_name}")
            
        except NoCredentialsError:
            raise StorageError("云存储凭据未配置")
        except Exception as e:
            raise StorageError(f"云存储初始化失败: {str(e)}")
    
    def _setup_aws_s3(self):
        """设置AWS S3客户端"""
        self.client = boto3.client(
            's3',
            aws_access_key_id=self.config.get('access_key_id'),
            aws_secret_access_key=self.config.get('secret_access_key'),
            region_name=self.region
        )
        
        # 确保存储桶存在
        self._ensure_bucket_exists()
    
    def _setup_ali_oss(self):
        """设置阿里云OSS客户端"""
        # 阿里云OSS使用S3兼容接口
        self.client = boto3.client(
            's3',
            aws_access_key_id=self.config.get('access_key_id'),
            aws_secret_access_key=self.config.get('secret_access_key'),
            endpoint_url=self.config.get('endpoint_url'),
            region_name=self.region
        )
        
        self._ensure_bucket_exists()
    
    def _setup_generic_s3(self):
        """设置通用S3兼容存储"""
        self.client = boto3.client(
            's3',
            aws_access_key_id=self.config.get('access_key_id'),
            aws_secret_access_key=self.config.get('secret_access_key'),
            endpoint_url=self.config.get('endpoint_url'),
            region_name=self.region
        )
        
        self._ensure_bucket_exists()
    
    def _ensure_bucket_exists(self):
        """确保存储桶存在"""
        try:
            self.client.head_bucket(Bucket=self.bucket_name)
        except ClientError as e:
            error_code = e.response['Error']['Code']
            if error_code == '404':
                # 存储桶不存在，尝试创建
                try:
                    if self.region == 'us-east-1':
                        self.client.create_bucket(Bucket=self.bucket_name)
                    else:
                        self.client.create_bucket(
                            Bucket=self.bucket_name,
                            CreateBucketConfiguration={
                                'LocationConstraint': self.region
                            }
                        )
                    current_app.logger.info(f"创建存储桶: {self.bucket_name}")
                except ClientError as create_error:
                    raise StorageError(f"创建存储桶失败: {str(create_error)}")
            else:
                raise StorageError(f"访问存储桶失败: {str(e)}")
    
    def save(self, file_data: BinaryIO, filename: str, 
             folder: str = '', metadata: Dict[str, Any] = None) -> Dict[str, Any]:
        """
        保存文件到云存储
        
        Args:
            file_data: 文件数据
            filename: 原始文件名
            folder: 存储文件夹
            metadata: 文件元数据
            
        Returns:
            dict: 文件信息
        """
        try:
            # 验证文件
            self.validate_file_size(file_data)
            self.validate_file_type(filename)
            
            # 生成安全文件名和对象键
            safe_filename = self.generate_filename(filename)
            object_key = self._generate_object_key(safe_filename, folder)
            
            # 获取文件信息
            file_info = self.get_file_info(file_data, filename)
            
            # 准备上传参数
            extra_args = {
                'Metadata': metadata or {},
                'ContentType': file_info['mime_type'],
            }
            
            # 重置文件指针
            file_data.seek(0)
            
            # 上传文件
            self.client.upload_fileobj(
                file_data,
                self.bucket_name,
                object_key,
                ExtraArgs=extra_args
            )
            
            # 构建返回信息
            result = {
                'file_id': object_key,
                'filename': safe_filename,
                'original_filename': filename,
                'folder': folder,
                'bucket': self.bucket_name,
                'object_key': object_key,
                'url': self.get_url(object_key),
                'size': file_info['file_size'],
                'mime_type': file_info['mime_type'],
                'extension': file_info['file_extension'],
                'created_at': datetime.utcnow().isoformat(),
                'provider': self.provider,
                'metadata': metadata or {}
            }
            
            current_app.logger.info(f"文件上传成功: {object_key}")
            return result
            
        except ClientError as e:
            current_app.logger.error(f"文件上传失败: {str(e)}")
            raise StorageError(f"文件上传失败: {str(e)}")
        except Exception as e:
            current_app.logger.error(f"文件上传失败: {str(e)}")
            raise StorageError(f"文件上传失败: {str(e)}")
    
    def get(self, file_id: str) -> BinaryIO:
        """
        从云存储获取文件
        
        Args:
            file_id: 对象键
            
        Returns:
            BinaryIO: 文件二进制流
        """
        try:
            import tempfile
            
            # 创建临时文件
            temp_file = tempfile.NamedTemporaryFile(delete=False)
            
            # 下载文件到临时文件
            self.client.download_fileobj(
                self.bucket_name,
                file_id,
                temp_file
            )
            
            # 重置文件指针
            temp_file.seek(0)
            
            return temp_file
            
        except ClientError as e:
            if e.response['Error']['Code'] == 'NoSuchKey':
                raise StorageError(f"文件不存在: {file_id}")
            else:
                current_app.logger.error(f"文件下载失败: {str(e)}")
                raise StorageError(f"文件下载失败: {str(e)}")
        except Exception as e:
            current_app.logger.error(f"文件下载失败: {str(e)}")
            raise StorageError(f"文件下载失败: {str(e)}")
    
    def delete(self, file_id: str) -> bool:
        """
        从云存储删除文件
        
        Args:
            file_id: 对象键
            
        Returns:
            bool: 是否删除成功
        """
        try:
            self.client.delete_object(
                Bucket=self.bucket_name,
                Key=file_id
            )
            
            current_app.logger.info(f"文件删除成功: {file_id}")
            return True
            
        except ClientError as e:
            current_app.logger.error(f"文件删除失败: {str(e)}")
            raise StorageError(f"文件删除失败: {str(e)}")
        except Exception as e:
            current_app.logger.error(f"文件删除失败: {str(e)}")
            raise StorageError(f"文件删除失败: {str(e)}")
    
    def exists(self, file_id: str) -> bool:
        """
        检查文件是否存在
        
        Args:
            file_id: 对象键
            
        Returns:
            bool: 文件是否存在
        """
        try:
            self.client.head_object(
                Bucket=self.bucket_name,
                Key=file_id
            )
            return True
        except ClientError as e:
            if e.response['Error']['Code'] == '404':
                return False
            else:
                raise StorageError(f"检查文件存在性失败: {str(e)}")
    
    def get_url(self, file_id: str, expires_in: int = 3600) -> str:
        """
        获取文件访问URL（预签名URL）
        
        Args:
            file_id: 对象键
            expires_in: URL过期时间（秒）
            
        Returns:
            str: 文件访问URL
        """
        try:
            if self.provider == 'ali_oss':
                # 阿里云OSS的特殊处理
                endpoint = self.config.get('endpoint_url', '').rstrip('/')
                return f"{endpoint}/{file_id}"
            
            # 生成预签名URL
            url = self.client.generate_presigned_url(
                'get_object',
                Params={
                    'Bucket': self.bucket_name,
                    'Key': file_id
                },
                ExpiresIn=expires_in
            )
            
            return url
            
        except Exception as e:
            current_app.logger.error(f"生成文件URL失败: {str(e)}")
            # 返回基本URL作为后备
            return f"https://{self.bucket_name}.s3.{self.region}.amazonaws.com/{file_id}"
    
    def get_metadata(self, file_id: str) -> Dict[str, Any]:
        """
        获取文件元数据
        
        Args:
            file_id: 对象键
            
        Returns:
            dict: 文件元数据
        """
        try:
            response = self.client.head_object(
                Bucket=self.bucket_name,
                Key=file_id
            )
            
            return {
                'file_id': file_id,
                'size': response['ContentLength'],
                'mime_type': response['ContentType'],
                'last_modified': response['LastModified'].isoformat(),
                'metadata': response.get('Metadata', {}),
                'etag': response['ETag'],
                'storage_class': response.get('StorageClass', 'STANDARD')
            }
            
        except ClientError as e:
            if e.response['Error']['Code'] == 'NoSuchKey':
                raise StorageError(f"文件不存在: {file_id}")
            else:
                current_app.logger.error(f"获取文件元数据失败: {str(e)}")
                raise StorageError(f"获取文件元数据失败: {str(e)}")
    
    def list_files(self, prefix: str = '', limit: int = 100) -> List[Dict[str, Any]]:
        """
        列出文件
        
        Args:
            prefix: 对象键前缀
            limit: 返回数量限制
            
        Returns:
            list: 文件信息列表
        """
        try:
            files = []
            paginator = self.client.get_paginator('list_objects_v2')
            
            page_iterator = paginator.paginate(
                Bucket=self.bucket_name,
                Prefix=prefix,
                PaginationConfig={'PageSize': limit}
            )
            
            for page in page_iterator:
                if 'Contents' in page:
                    for obj in page['Contents']:
                        files.append({
                            'file_id': obj['Key'],
                            'filename': os.path.basename(obj['Key']),
                            'size': obj['Size'],
                            'last_modified': obj['LastModified'].isoformat(),
                            'etag': obj['ETag'],
                            'storage_class': obj.get('StorageClass', 'STANDARD')
                        })
                
                if len(files) >= limit:
                    break
            
            return files[:limit]
            
        except Exception as e:
            current_app.logger.error(f"列出文件失败: {str(e)}")
            raise StorageError(f"列出文件失败: {str(e)}")
    
    def _generate_object_key(self, filename: str, folder: str = '') -> str:
        """
        生成对象键
        
        Args:
            filename: 文件名
            folder: 文件夹
            
        Returns:
            str: 对象键
        """
        if folder:
            # 确保文件夹路径以/结尾
            if not folder.endswith('/'):
                folder += '/'
            return f"{folder}{filename}"
        else:
            return filename
    
    def copy_file(self, source_key: str, destination_key: str) -> bool:
        """
        复制文件
        
        Args:
            source_key: 源对象键
            destination_key: 目标对象键
            
        Returns:
            bool: 是否复制成功
        """
        try:
            self.client.copy_object(
                Bucket=self.bucket_name,
                CopySource={'Bucket': self.bucket_name, 'Key': source_key},
                Key=destination_key
            )
            return True
        except Exception as e:
            current_app.logger.error(f"文件复制失败: {str(e)}")
            raise StorageError(f"文件复制失败: {str(e)}")
    
    def get_bucket_info(self) -> Dict[str, Any]:
        """
        获取存储桶信息
        
        Returns:
            dict: 存储桶信息
        """
        try:
            response = self.client.list_objects_v2(
                Bucket=self.bucket_name,
                MaxKeys=0
            )
            
            return {
                'bucket_name': self.bucket_name,
                'key_count': response.get('KeyCount', 0),
                'max_keys': response.get('MaxKeys', 0),
                'is_truncated': response.get('IsTruncated', False)
            }
        except Exception as e:
            current_app.logger.error(f"获取存储桶信息失败: {str(e)}")
            return {
                'bucket_name': self.bucket_name,
                'error': str(e)
            }
    
    def set_file_acl(self, file_id: str, acl: str = 'private') -> bool:
        """
        设置文件ACL
        
        Args:
            file_id: 对象键
            acl: 访问控制列表（private, public-read, public-read-write）
            
        Returns:
            bool: 是否设置成功
        """
        try:
            self.client.put_object_acl(
                Bucket=self.bucket_name,
                Key=file_id,
                ACL=acl
            )
            return True
        except Exception as e:
            current_app.logger.error(f"设置文件ACL失败: {str(e)}")
            raise StorageError(f"设置文件ACL失败: {str(e)}")